home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / non-ANSI / c-client / mh.c < prev    next >
C/C++ Source or Header  |  1996-05-15  |  45KB  |  1,648 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    23 February 1992
  13.  * Last Edited:    15 May 1996
  14.  *
  15.  * Copyright 1996 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36. #include <stdio.h>
  37. #include <ctype.h>
  38. #include <errno.h>
  39. extern int errno;        /* just in case */
  40. #include "mail.h"
  41. #include "osdep.h"
  42. #include <pwd.h>
  43. #include <sys/stat.h>
  44. #include <sys/time.h>
  45. #include "mh.h"
  46. #include "rfc822.h"
  47. #include "misc.h"
  48. #include "dummy.h"
  49.  
  50. /* MH mail routines */
  51.  
  52.  
  53. /* Driver dispatch used by MAIL */
  54.  
  55. DRIVER mhdriver = {
  56.   "mh",                /* driver name */
  57.   (DRIVER *) NIL,        /* next driver */
  58.   mh_valid,            /* mailbox is valid for us */
  59.   mh_parameters,        /* manipulate parameters */
  60.   mh_find,            /* find mailboxes */
  61.   mh_find_bboards,        /* find bboards */
  62.   mh_find_all,            /* find all mailboxes */
  63.   mh_find_all_bboards,        /* find all bboards */
  64.   mh_subscribe,            /* subscribe to mailbox */
  65.   mh_unsubscribe,        /* unsubscribe from mailbox */
  66.   mh_subscribe_bboard,        /* subscribe to bboard */
  67.   mh_unsubscribe_bboard,    /* unsubscribe from bboard */
  68.   mh_create,            /* create mailbox */
  69.   mh_delete,            /* delete mailbox */
  70.   mh_rename,            /* rename mailbox */
  71.   mh_open,            /* open mailbox */
  72.   mh_close,            /* close mailbox */
  73.   mh_fetchfast,            /* fetch message "fast" attributes */
  74.   mh_fetchflags,        /* fetch message flags */
  75.   mh_fetchstructure,        /* fetch message envelopes */
  76.   mh_fetchheader,        /* fetch message header only */
  77.   mh_fetchtext,            /* fetch message body only */
  78.   mh_fetchbody,            /* fetch message body section */
  79.   mh_setflag,            /* set message flag */
  80.   mh_clearflag,            /* clear message flag */
  81.   mh_search,            /* search for message based on criteria */
  82.   mh_ping,            /* ping mailbox to see if still alive */
  83.   mh_check,            /* check for new messages */
  84.   mh_expunge,            /* expunge deleted messages */
  85.   mh_copy,            /* copy messages to another mailbox */
  86.   mh_move,            /* move messages to another mailbox */
  87.   mh_append,            /* append string message to mailbox */
  88.   mh_gc                /* garbage collect stream */
  89. };
  90.  
  91.                 /* prototype stream */
  92. MAILSTREAM mhproto = {&mhdriver};
  93.  
  94.  
  95. /* MH mail validate mailbox
  96.  * Accepts: mailbox name
  97.  * Returns: our driver if name is valid, NIL otherwise
  98.  */
  99.  
  100. DRIVER *mh_valid (name)
  101.     char *name;
  102. {
  103.   char tmp[MAILTMPLEN];
  104.   return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
  105. }
  106.  
  107. /* MH mail test for valid mailbox
  108.  * Accepts: mailbox name
  109.  *        temporary buffer to use
  110.  *        syntax only test flag
  111.  * Returns: T if valid, NIL otherwise
  112.  */
  113.  
  114. static char *mh_path = NIL;    /* holds MH path name */
  115. static long mh_once = 0;    /* already through this code */
  116.  
  117. int mh_isvalid (name,tmp,synonly)
  118.     char *name;
  119.     char *tmp;
  120.     long synonly;
  121. {
  122.   struct stat sbuf;
  123.   if (!mh_path) {        /* have MH path yet? */
  124.     char *s,*s1,*t,*v;
  125.     int fd;
  126.     if (mh_once++) return NIL;    /* only do this code once */
  127.     sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
  128.     if ((fd = open (tmp,O_RDONLY,NIL)) < 0) return NIL;
  129.     fstat (fd,&sbuf);        /* yes, get size and read file */
  130.     read (fd,(s1 = t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
  131.     close (fd);            /* don't need the file any more */
  132.     t[sbuf.st_size] = '\0';    /* tie it off */
  133.                 /* parse profile file */
  134.     while (*(s = t) && (t = strchr (s,'\n'))) {
  135.       *t++ = '\0';        /* tie off line */
  136.                 /* found space in line? */
  137.       if (v = strpbrk (s," \t")) {
  138.     *v = '\0';        /* tie off, is keyword "Path:"? */
  139.     if (!strcmp (lcase (s),"path:")) {
  140.       if (*++v == '/') s = v;
  141.       else sprintf (s = tmp,"%s/%s",myhomedir (),v);
  142.       mh_path = cpystr (s);    /* copy name */
  143.       break;        /* don't need to look at rest of file */
  144.     }
  145.       }
  146.     }
  147.     fs_give ((void **) &s1);    /* flush profile text */
  148.     if (!mh_path) {        /* default path if not in the profile */
  149.       sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
  150.       mh_path = cpystr (tmp);
  151.     }
  152.   }
  153.  
  154.                 /* name must be #MHINBOX or #mh/... */
  155.   if (strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") &&
  156.       !(tmp[0] == '#' && tmp[1] == 'M' && tmp[2] == 'H' && tmp[3] == '/')) {
  157.     errno = EINVAL;        /* bogus name */
  158.     return NIL;
  159.   }
  160.                 /* all done if syntax only check */
  161.   if (synonly && tmp[0] == '#') return T;
  162.   errno = NIL;            /* zap error */
  163.                 /* validate name as directory */
  164.   return ((stat (mh_file (tmp,name),&sbuf) == 0) &&
  165.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  166. }
  167.  
  168.  
  169. /* MH manipulate driver parameters
  170.  * Accepts: function code
  171.  *        function-dependent value
  172.  * Returns: function-dependent return value
  173.  */
  174.  
  175. void *mh_parameters (function,value)
  176.     long function;
  177.     void *value;
  178. {
  179.   return NIL;
  180. }
  181.  
  182. /* MH mail find list of mailboxes
  183.  * Accepts: mail stream
  184.  *        pattern to search
  185.  */
  186.  
  187. void mh_find (stream,pat)
  188.     MAILSTREAM *stream;
  189.     char *pat;
  190. {
  191.   void *s = NIL;
  192.   char *t,tmp[MAILTMPLEN];
  193.                 /* read subscription database */
  194.   if (stream) while (t = sm_read (&s))
  195.     if (pmatch (t,pat) && mh_isvalid (t,tmp,T)) mm_mailbox (t);
  196. }
  197.  
  198.  
  199. /* MH mail find list of bboards
  200.  * Accepts: mail stream
  201.  *        pattern to search
  202.  */
  203.  
  204. void mh_find_bboards (stream,pat)
  205.     MAILSTREAM *stream;
  206.     char *pat;
  207. {
  208.   /* Always a no-op */
  209. }
  210.  
  211. /* MH mail find list of all mailboxes
  212.  * Accepts: mail stream
  213.  *        pattern to search
  214.  */
  215.  
  216. void mh_find_all (stream,pat)
  217.     MAILSTREAM *stream;
  218.     char *pat;
  219. {
  220.   DIR *dirp;
  221.   struct direct *d;
  222.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  223.   int i = 0;
  224.   char *s,*t;
  225.   if (!(pat[0] == '#' && (pat[1] == 'm' || pat[1] == 'M') &&
  226.     (pat[2] == 'h' || pat[2] == 'H') && pat[3] == '/')) return;
  227.                 /* must have a path */
  228.   if (!mh_path) mh_isvalid ("#MHINBOX",tmp,T);
  229.   if (!mh_path) return;        /* sorry */
  230.   memset (tmp,'\0',MAILTMPLEN);    /* init directory */
  231.   strcpy (tmp,mh_path);
  232.   memset (file,'\0',MAILTMPLEN);/* and mh mailbox name */
  233.   strcpy (file,"#mh/");
  234.                 /* directory specified in pattern, copy it */
  235.   if (s = strrchr (pat + 4,'/')) {
  236.     strncat (tmp,pat + 3,i = s - (pat + 3));
  237.     strncat (file,pat + 4,i);
  238.   }
  239.   i = strlen (file);        /* length of prefix */
  240.   if (dirp = opendir (tmp)) {    /* now open that directory */
  241.     while (d = readdir (dirp)) {/* for each directory entry */
  242.       strcpy (file + i,d->d_name);
  243.       if (((d->d_name[0] != '.') ||
  244.        (d->d_name[1] && ((d->d_name[1] != '.') || d->d_name[2]))) &&
  245.       pmatch (file,pat) && (mh_isvalid (file,tmp,NIL))) mm_mailbox (file);
  246.     }
  247.     closedir (dirp);        /* flush directory */
  248.   }
  249. }
  250.  
  251.  
  252. /* MH mail find list of all bboards
  253.  * Accepts: mail stream
  254.  *        pattern to search
  255.  */
  256.  
  257. void mh_find_all_bboards (stream,pat)
  258.     MAILSTREAM *stream;
  259.     char *pat;
  260. {
  261.   /* Always a no-op */
  262. }
  263.  
  264. /* MH mail subscribe to mailbox
  265.  * Accepts: mail stream
  266.  *        mailbox to add to subscription list
  267.  * Returns: T on success, NIL on failure
  268.  */
  269.  
  270. long mh_subscribe (stream,mailbox)
  271.     MAILSTREAM *stream;
  272.     char *mailbox;
  273. {
  274.   char tmp[MAILTMPLEN];
  275.   return sm_subscribe (mailbox);
  276. }
  277.  
  278.  
  279. /* MH mail unsubscribe to mailbox
  280.  * Accepts: mail stream
  281.  *        mailbox to delete from subscription list
  282.  * Returns: T on success, NIL on failure
  283.  */
  284.  
  285. long mh_unsubscribe (stream,mailbox)
  286.     MAILSTREAM *stream;
  287.     char *mailbox;
  288. {
  289.   char tmp[MAILTMPLEN];
  290.   return sm_unsubscribe (mailbox);
  291. }
  292.  
  293.  
  294. /* MH mail subscribe to bboard
  295.  * Accepts: mail stream
  296.  *        bboard to add to subscription list
  297.  * Returns: T on success, NIL on failure
  298.  */
  299.  
  300. long mh_subscribe_bboard (stream,mailbox)
  301.     MAILSTREAM *stream;
  302.     char *mailbox;
  303. {
  304.   return NIL;            /* never valid for MH */
  305. }
  306.  
  307.  
  308. /* MH mail unsubscribe to bboard
  309.  * Accepts: mail stream
  310.  *        bboard to delete from subscription list
  311.  * Returns: T on success, NIL on failure
  312.  */
  313.  
  314. long mh_unsubscribe_bboard (stream,mailbox)
  315.     MAILSTREAM *stream;
  316.     char *mailbox;
  317. {
  318.   return NIL;            /* never valid for MH */
  319. }
  320.  
  321. /* MH mail create mailbox
  322.  * Accepts: mail stream
  323.  *        mailbox name to create
  324.  * Returns: T on success, NIL on failure
  325.  */
  326.  
  327. long mh_create (stream,mailbox)
  328.     MAILSTREAM *stream;
  329.     char *mailbox;
  330. {
  331.   char tmp[MAILTMPLEN];
  332.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  333.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  334.     sprintf (tmp,"Can't create mailbox %s: invalid MH-format name",mailbox);
  335.     mm_log (tmp,ERROR);
  336.     return NIL;
  337.   }
  338.                 /* must not already exist */
  339.   if (mh_isvalid (mailbox,tmp,NIL)) {
  340.     sprintf (tmp,"Can't create mailbox %s: mailbox already exists",mailbox);
  341.     mm_log (tmp,ERROR);
  342.     return NIL;
  343.   }
  344.   if (!mh_path) return NIL;    /* sorry */
  345.   sprintf (tmp,"%s/%s",mh_path,mailbox + 4);
  346.   if (mkdir (tmp,0700)) {    /* try to make it */
  347.     sprintf (tmp,"Can't create mailbox %s: %s",mailbox,strerror (errno));
  348.     mm_log (tmp,ERROR);
  349.     return NIL;
  350.   }
  351.   return T;            /* return success */
  352. }
  353.  
  354. /* MH mail delete mailbox
  355.  *        mailbox name to delete
  356.  * Returns: T on success, NIL on failure
  357.  */
  358.  
  359. long mh_delete (stream,mailbox)
  360.     MAILSTREAM *stream;
  361.     char *mailbox;
  362. {
  363.   DIR *dirp;
  364.   struct direct *d;
  365.   int i;
  366.   char tmp[MAILTMPLEN];
  367.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  368.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  369.     sprintf (tmp,"Can't delete mailbox %s: invalid MH-format name",mailbox);
  370.     mm_log (tmp,ERROR);
  371.     return NIL;
  372.   }
  373.                 /* is mailbox valid? */
  374.   if (!mh_isvalid (mailbox,tmp,NIL)){
  375.     sprintf (tmp,"Can't delete mailbox %s: no such mailbox",mailbox);
  376.     mm_log (tmp,ERROR);
  377.     return NIL;
  378.   }
  379.                 /* get name of directory */
  380.   i = strlen (mh_file (tmp,mailbox));
  381.   if (dirp = opendir (tmp)) {    /* open directory */
  382.     tmp[i++] = '/';        /* now apply trailing delimiter */
  383.     while (d = readdir (dirp))    /* massacre all numeric or comma files */
  384.       if (mh_select (d) || *d->d_name == ',') {
  385.     strcpy (tmp + i,d->d_name);
  386.     unlink (tmp);        /* sayonara */
  387.       }
  388.     closedir (dirp);        /* flush directory */
  389.   }
  390.                 /* try to remove the directory */
  391.   if (rmdir (mh_file (tmp,mailbox))) {
  392.     sprintf (tmp,"Can't delete mailbox %s: %s",mailbox,strerror (errno));
  393.     mm_log (tmp,ERROR);
  394.     return NIL;
  395.   }
  396.   return T;            /* return success */
  397. }
  398.  
  399. /* MH mail rename mailbox
  400.  * Accepts: MH mail stream
  401.  *        old mailbox name
  402.  *        new mailbox name
  403.  * Returns: T on success, NIL on failure
  404.  */
  405.  
  406. long mh_rename (stream,old,new)
  407.     MAILSTREAM *stream;
  408.     char *old;
  409.     char *new;
  410. {
  411.   char tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
  412.   if (!(old[0] == '#' && (old[1] == 'm' || old[1] == 'M') &&
  413.     (old[2] == 'h' || old[2] == 'H') && old[3] == '/')) {
  414.     sprintf (tmp,"Can't delete mailbox %s: invalid MH-format name",old);
  415.     mm_log (tmp,ERROR);
  416.     return NIL;
  417.   }
  418.                 /* old mailbox name must be valid */
  419.   if (!mh_isvalid (old,tmp,NIL)) {
  420.     sprintf (tmp,"Can't rename mailbox %s: no such mailbox",old);
  421.     mm_log (tmp,ERROR);
  422.     return NIL;
  423.   }
  424.   if (!(new[0] == '#' && (new[1] == 'm' || new[1] == 'M') &&
  425.     (new[2] == 'h' || new[2] == 'H') && new[3] == '/')) {
  426.     sprintf (tmp,"Can't rename to mailbox %s: invalid MH-format name",new);
  427.     mm_log (tmp,ERROR);
  428.     return NIL;
  429.   }
  430.   if (mh_isvalid (new,tmp,NIL)){/* new mailbox name must not be valid */
  431.     sprintf (tmp,"Can't rename to mailbox %s: destination already exists",new);
  432.     mm_log (tmp,ERROR);
  433.     return NIL;
  434.   }
  435.                 /* try to rename the directory */
  436.   if (rename (mh_file (tmp,old),mh_file (tmp1,new))) {
  437.     sprintf (tmp,"Can't rename mailbox %s to %s: %s",old,new,strerror (errno));
  438.     mm_log (tmp,ERROR);
  439.     return NIL;
  440.   }
  441.   return T;            /* return success */
  442. }
  443.  
  444. /* MH mail open
  445.  * Accepts: stream to open
  446.  * Returns: stream on success, NIL on failure
  447.  */
  448.  
  449. MAILSTREAM *mh_open (stream)
  450.     MAILSTREAM *stream;
  451. {
  452.   char tmp[MAILTMPLEN];
  453.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  454.   if (LOCAL) {            /* close old file if stream being recycled */
  455.     mh_close (stream);        /* dump and save the changes */
  456.     stream->dtb = &mhdriver;    /* reattach this driver */
  457.     mail_free_cache (stream);    /* clean up cache */
  458.   }
  459.   stream->local = fs_get (sizeof (MHLOCAL));
  460.                 /* note if an INBOX or not */
  461.   LOCAL->inbox = !strcmp (ucase (strcpy (tmp,stream->mailbox)),"#MHINBOX");
  462.   mh_file (tmp,stream->mailbox);/* get directory name */
  463.   LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  464.   LOCAL->hdr = NIL;        /* no current header */
  465.                 /* make temporary buffer */
  466.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  467.   LOCAL->scantime = 0;        /* not scanned yet */
  468.   stream->sequence++;        /* bump sequence number */
  469.                 /* parse mailbox */
  470.   stream->nmsgs = stream->recent = 0;
  471.   if (mh_ping (stream) && !(stream->nmsgs || stream->silent))
  472.     mm_log ("Mailbox is empty",(long) NIL);
  473.   return stream;        /* return stream to caller */
  474. }
  475.  
  476. /* MH mail close
  477.  * Accepts: MAIL stream
  478.  */
  479.  
  480. void mh_close (stream)
  481.     MAILSTREAM *stream;
  482. {
  483.   if (LOCAL) {            /* only if a file is open */
  484.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  485.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  486.                 /* free local scratch buffer */
  487.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  488.                 /* nuke the local data */
  489.     fs_give ((void **) &stream->local);
  490.     stream->dtb = NIL;        /* log out the DTB */
  491.   }
  492. }
  493.  
  494.  
  495. /* MH mail fetch fast information
  496.  * Accepts: MAIL stream
  497.  *        sequence
  498.  */
  499.  
  500. void mh_fetchfast (stream,sequence)
  501.     MAILSTREAM *stream;
  502.     char *sequence;
  503. {
  504.   long i;
  505.                 /* ugly and slow */
  506.   if (stream && LOCAL && mail_sequence (stream,sequence))
  507.     for (i = 1; i <= stream->nmsgs; i++)
  508.       if (mail_elt (stream,i)->sequence)
  509.     mh_fetchheader (stream,i);
  510. }
  511.  
  512.  
  513. /* MH mail fetch flags
  514.  * Accepts: MAIL stream
  515.  *        sequence
  516.  */
  517.  
  518. void mh_fetchflags (stream,sequence)
  519.     MAILSTREAM *stream;
  520.     char *sequence;
  521. {
  522.   return;            /* no-op for local mail */
  523. }
  524.  
  525. /* MH mail fetch message structure
  526.  * Accepts: MAIL stream
  527.  *        message # to fetch
  528.  *        pointer to return body
  529.  * Returns: envelope of this message, body returned in body value
  530.  *
  531.  * Fetches the "fast" information as well
  532.  */
  533.  
  534. ENVELOPE *mh_fetchstructure (stream,msgno,body)
  535.     MAILSTREAM *stream;
  536.     long msgno;
  537.     BODY **body;
  538. {
  539.   char *h,*t;
  540.   LONGCACHE *lelt;
  541.   ENVELOPE **env;
  542.   STRING bs;
  543.   BODY **b;
  544.   if (stream->scache) {        /* short cache */
  545.     if (msgno != stream->msgno){/* flush old poop if a different message */
  546.       mail_free_envelope (&stream->env);
  547.       mail_free_body (&stream->body);
  548.     }
  549.     stream->msgno = msgno;
  550.     env = &stream->env;        /* get pointers to envelope and body */
  551.     b = &stream->body;
  552.   }
  553.   else {            /* long cache */
  554.     lelt = mail_lelt (stream,msgno);
  555.     env = &lelt->env;        /* get pointers to envelope and body */
  556.     b = &lelt->body;
  557.   }
  558.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  559.     mail_free_envelope (env);    /* flush old envelope and body */
  560.     mail_free_body (b);
  561.     h = mh_fetchheader (stream,msgno);
  562.                 /* can't use fetchtext since it'll set seen */
  563.     t = stream->text ? stream->text : "";
  564.     INIT (&bs,mail_string,(void *) t,strlen (t));
  565.                 /* parse envelope and body */
  566.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,mylocalhost (),
  567.               LOCAL->buf);
  568.   }
  569.   if (body) *body = *b;        /* return the body */
  570.   return *env;            /* return the envelope */
  571. }
  572.  
  573. /* MH mail fetch message header
  574.  * Accepts: MAIL stream
  575.  *        message # to fetch
  576.  * Returns: message header in RFC822 format
  577.  */
  578.  
  579. char *mh_fetchheader (stream,msgno)
  580.     MAILSTREAM *stream;
  581.     long msgno;
  582. {
  583.   unsigned long i,hdrsize;
  584.   int fd;
  585.   char *s,*b,*t;
  586.   long m = msgno - 1;
  587.   struct stat sbuf;
  588.   struct tm *tm;
  589.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  590.                 /* build message file name */
  591.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  592.   if (stream->msgno != msgno) {
  593.     mh_gc (stream,GC_TEXTS);    /* invalidate current cache */
  594.     if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0) {
  595.       fstat (fd,&sbuf);        /* get size of message */
  596.                 /* make plausible IMAPish date string */
  597.       tm = gmtime (&sbuf.st_mtime);
  598.       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  599.       elt->year = tm->tm_year + 1900 - BASEYEAR;
  600.       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  601.       elt->seconds = tm->tm_sec;
  602.       elt->zhours = 0; elt->zminutes = 0;
  603.                 /* slurp message */
  604.       read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  605.       s[sbuf.st_size] = '\0';    /* tie off file */
  606.       close (fd);        /* flush message file */
  607.       stream->msgno = msgno;    /* note current message number */
  608.                 /* find end of header */
  609.       for (i = 0,b = s; *b && !(i && (*b == '\n')); i = (*b++ == '\n'));
  610.       hdrsize = (*b ? ++b:b)-s; /* number of header bytes */
  611.       elt->rfc822_size =    /* size of entire message in CRLF form */
  612.     strcrlfcpy (&LOCAL->hdr,&i,s,hdrsize) +
  613.     strcrlfcpy (&stream->text,&i,b,sbuf.st_size - hdrsize);
  614.       fs_give ((void **) &s);    /* flush old data */
  615.     }
  616.   }
  617.   return LOCAL->hdr ? LOCAL->hdr : "";
  618. }
  619.  
  620. /* MH mail fetch message text (only)
  621.     body only;
  622.  * Accepts: MAIL stream
  623.  *        message # to fetch
  624.  * Returns: message text in RFC822 format
  625.  */
  626.  
  627. char *mh_fetchtext (stream,msgno)
  628.     MAILSTREAM *stream;
  629.     long msgno;
  630. {
  631.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  632.                 /* snarf message if don't have it yet */
  633.   if (stream->msgno != msgno) mh_fetchheader (stream,msgno);
  634.   if (!elt->seen) elt->seen = T;/* mark as seen */
  635.   return stream->text ? stream->text : "";
  636. }
  637.  
  638. /* MH fetch message body as a structure
  639.  * Accepts: Mail stream
  640.  *        message # to fetch
  641.  *        section specifier
  642.  *        pointer to length
  643.  * Returns: pointer to section of message body
  644.  */
  645.  
  646. char *mh_fetchbody (stream,m,s,len)
  647.     MAILSTREAM *stream;
  648.     long m;
  649.     char *s;
  650.     unsigned long *len;
  651. {
  652.   BODY *b;
  653.   PART *pt;
  654.   unsigned long i;
  655.   char *base;
  656.   unsigned long offset = 0;
  657.   MESSAGECACHE *elt = mail_elt (stream,m);
  658.                 /* make sure have a body */
  659.   if (!(mh_fetchstructure (stream,m,&b) && b && s && *s &&
  660.     ((i = strtol (s,&s,10)) > 0) && (base = mh_fetchtext (stream,m))))
  661.     return NIL;
  662.   do {                /* until find desired body part */
  663.                 /* multipart content? */
  664.     if (b->type == TYPEMULTIPART) {
  665.       pt = b->contents.part;    /* yes, find desired part */
  666.       while (--i && (pt = pt->next));
  667.       if (!pt) return NIL;    /* bad specifier */
  668.                 /* note new body, check valid nesting */
  669.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  670.       offset = pt->offset;    /* get new offset */
  671.     }
  672.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  673.                 /* need to go down further? */
  674.     if (i = *s) switch (b->type) {
  675.     case TYPEMESSAGE:        /* embedded message */
  676.       offset = b->contents.msg.offset;
  677.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  678.     case TYPEMULTIPART:        /* multipart, get next section */
  679.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  680.     default:            /* bogus subpart specification */
  681.       return NIL;
  682.     }
  683.   } while (i);
  684.                 /* lose if body bogus */
  685.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  686.   if (!elt->seen) elt->seen = T;/* mark as seen */
  687.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  688.               b->size.ibytes,b->encoding);
  689. }
  690.  
  691. /* MH mail set flag
  692.  * Accepts: MAIL stream
  693.  *        sequence
  694.  *        flag(s)
  695.  */
  696.  
  697. void mh_setflag (stream,sequence,flag)
  698.     MAILSTREAM *stream;
  699.     char *sequence;
  700.     char *flag;
  701. {
  702.   MESSAGECACHE *elt;
  703.   long i;
  704.   short f = mh_getflags (stream,flag);
  705.   if (!f) return;        /* no-op if no flags to modify */
  706.                 /* get sequence and loop on it */
  707.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  708.     if ((elt = mail_elt (stream,i))->sequence) {
  709.                 /* set all requested flags */
  710.       if (f&fSEEN) elt->seen = T;
  711.       if (f&fDELETED) elt->deleted = T;
  712.       if (f&fFLAGGED) elt->flagged = T;
  713.       if (f&fANSWERED) elt->answered = T;
  714.     }
  715. }
  716.  
  717.  
  718. /* MH mail clear flag
  719.  * Accepts: MAIL stream
  720.  *        sequence
  721.  *        flag(s)
  722.  */
  723.  
  724. void mh_clearflag (stream,sequence,flag)
  725.     MAILSTREAM *stream;
  726.     char *sequence;
  727.     char *flag;
  728. {
  729.   MESSAGECACHE *elt;
  730.   long i = stream->nmsgs;
  731.   short f = mh_getflags (stream,flag);
  732.   if (!f) return;        /* no-op if no flags to modify */
  733.                 /* get sequence and loop on it */
  734.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  735.     if ((elt = mail_elt (stream,i))->sequence) {
  736.                 /* clear all requested flags */
  737.       if (f&fSEEN) elt->seen = NIL;
  738.       if (f&fDELETED) elt->deleted = NIL;
  739.       if (f&fFLAGGED) elt->flagged = NIL;
  740.       if (f&fANSWERED) elt->answered = NIL;
  741.                 /* clearing either seen or deleted does this */
  742.     }
  743. }
  744.  
  745. /* MH mail search for messages
  746.  * Accepts: MAIL stream
  747.  *        search criteria
  748.  */
  749.  
  750. void mh_search (stream,criteria)
  751.     MAILSTREAM *stream;
  752.     char *criteria;
  753. {
  754.   long i,n;
  755.   char *d;
  756.   search_t f;
  757.                 /* initially all searched */
  758.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  759.                 /* get first criterion */
  760.   if (criteria && (criteria = strtok (criteria," "))) {
  761.                 /* for each criterion */
  762.     for (; criteria; (criteria = strtok (NIL," "))) {
  763.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  764.       switch (*ucase (criteria)) {
  765.       case 'A':            /* possible ALL, ANSWERED */
  766.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  767.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  768.     break;
  769.       case 'B':            /* possible BCC, BEFORE, BODY */
  770.     if (!strcmp (criteria+1,"CC"))
  771.       f = mh_search_string (mh_search_bcc,&d,&n);
  772.     else if (!strcmp (criteria+1,"EFORE"))
  773.       f = mh_search_date (mh_search_before,&n);
  774.     else if (!strcmp (criteria+1,"ODY"))
  775.       f = mh_search_string (mh_search_body,&d,&n);
  776.     break;
  777.       case 'C':            /* possible CC */
  778.     if (!strcmp (criteria+1,"C"))
  779.       f = mh_search_string (mh_search_cc,&d,&n);
  780.     break;
  781.       case 'D':            /* possible DELETED */
  782.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  783.     break;
  784.       case 'F':            /* possible FLAGGED, FROM */
  785.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  786.     else if (!strcmp (criteria+1,"ROM"))
  787.       f = mh_search_string (mh_search_from,&d,&n);
  788.     break;
  789.       case 'K':            /* possible KEYWORD */
  790.     if (!strcmp (criteria+1,"EYWORD"))
  791.       f = mh_search_flag (mh_search_keyword,&d);
  792.     break;
  793.       case 'N':            /* possible NEW */
  794.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  795.     break;
  796.  
  797.       case 'O':            /* possible OLD, ON */
  798.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  799.     else if (!strcmp (criteria+1,"N"))
  800.       f = mh_search_date (mh_search_on,&n);
  801.     break;
  802.       case 'R':            /* possible RECENT */
  803.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  804.     break;
  805.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  806.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  807.     else if (!strcmp (criteria+1,"INCE"))
  808.       f = mh_search_date (mh_search_since,&n);
  809.     else if (!strcmp (criteria+1,"UBJECT"))
  810.       f = mh_search_string (mh_search_subject,&d,&n);
  811.     break;
  812.       case 'T':            /* possible TEXT, TO */
  813.     if (!strcmp (criteria+1,"EXT"))
  814.       f = mh_search_string (mh_search_text,&d,&n);
  815.     else if (!strcmp (criteria+1,"O"))
  816.       f = mh_search_string (mh_search_to,&d,&n);
  817.     break;
  818.       case 'U':            /* possible UN* */
  819.     if (criteria[1] == 'N') {
  820.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  821.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  822.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  823.       else if (!strcmp (criteria+2,"KEYWORD"))
  824.         f = mh_search_flag (mh_search_unkeyword,&d);
  825.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  826.     }
  827.     break;
  828.       default:            /* we will barf below */
  829.     break;
  830.       }
  831.       if (!f) {            /* if can't determine any criteria */
  832.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  833.     mm_log (LOCAL->buf,ERROR);
  834.     return;
  835.       }
  836.                 /* run the search criterion */
  837.       for (i = 1; i <= stream->nmsgs; ++i)
  838.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  839.       mail_elt (stream,i)->searched = NIL;
  840.     }
  841.                 /* report search results to main program */
  842.     for (i = 1; i <= stream->nmsgs; ++i)
  843.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  844.   }
  845. }
  846.  
  847. /* MH mail ping mailbox
  848.  * Accepts: MAIL stream
  849.  * Returns: T if stream alive, else NIL
  850.  */
  851.  
  852. long mh_ping (stream)
  853.     MAILSTREAM *stream;
  854. {
  855.   MAILSTREAM *sysibx = NIL;
  856.   MESSAGECACHE *elt,*selt;
  857.   struct stat sbuf;
  858.   char *s,tmp[MAILTMPLEN];
  859.   int fd;
  860.   long i,j,r,old;
  861.   long nmsgs = stream->nmsgs;
  862.   long recent = stream->recent;
  863.   stat (LOCAL->dir,&sbuf);
  864.   if (sbuf.st_ctime != LOCAL->scantime) {
  865.     struct direct **names = NIL;
  866.     long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
  867.     old = nmsgs ? mail_elt (stream,nmsgs)->data1 : 0;
  868.                 /* note scanned now */
  869.     LOCAL->scantime = sbuf.st_ctime;
  870.                 /* scan directory */
  871.     for (i = 0; i < nfiles; ++i) {
  872.                 /* if newly seen, add to list */
  873.       if ((j = atoi (names[i]->d_name)) > old) {
  874.     (elt = mail_elt (stream,++nmsgs))->data1 = j;
  875.     elt->valid = T;        /* note valid flags */
  876.     if (old) {        /* other than the first pass? */
  877.       elt->recent = T;    /* yup, mark as recent */
  878.       recent++;        /* bump recent count */
  879.     }
  880.     else {            /* see if already read */
  881.       sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
  882.       stat (tmp,&sbuf);    /* get inode poop */
  883.       if (sbuf.st_atime > sbuf.st_mtime) elt->seen = T;
  884.     }
  885.       }
  886.       fs_give ((void **) &names[i]);
  887.     }
  888.                 /* free directory */
  889.     if (names) fs_give ((void **) &names);
  890.   }
  891.  
  892.   if (LOCAL->inbox) {        /* if INBOX, snarf from system INBOX  */
  893.     old = nmsgs ? mail_elt (stream,nmsgs)->data1 : 0;
  894.                 /* paranoia check */
  895.     if (!strcmp (sysinbox (),stream->mailbox)) return NIL;
  896.     mm_critical (stream);    /* go critical */
  897.     stat (sysinbox (),&sbuf);    /* see if anything there */
  898.                 /* can get sysinbox mailbox? */
  899.     if (sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT))
  900.     && (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
  901.       for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
  902.                 /* build file name we will use */
  903.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
  904.                 /* snarf message from Berkeley mailbox */
  905.     s = bezerk_snarf (sysibx,i,&j);
  906.     selt = mail_elt (sysibx,i);
  907.     if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
  908.              S_IREAD|S_IWRITE)) >= 0) &&
  909.         (write (fd,s,j) == j) && (!fsync (fd)) && (!close (fd))) {
  910.                 /* create new elt, note its file number */
  911.       (elt = mail_elt (stream,++nmsgs))->data1 = old;
  912.       recent++;        /* bump recent count */
  913.                 /* set up initial flags and date */
  914.       elt->valid = elt->recent = T;
  915.       elt->seen = selt->seen;
  916.       elt->deleted = selt->deleted;
  917.       elt->flagged = selt->flagged;
  918.       elt->answered = selt->answered;
  919.       elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
  920.       elt->hours = selt->hours;elt->minutes = selt->minutes;
  921.       elt->seconds = selt->seconds;
  922.       elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
  923.       /* should set the file date too */
  924.     }
  925.     else {            /* failed to snarf */
  926.       if (fd) {        /* did it ever get opened? */
  927.         close (fd);        /* close descriptor */
  928.         unlink (LOCAL->buf);/* flush this file */
  929.       }
  930.       return NIL;        /* note that something is badly wrong */
  931.     }
  932.     selt->deleted = T;    /* delete it from the sysinbox */
  933.       }
  934.       stat (LOCAL->dir,&sbuf);    /* update scan time */
  935.       LOCAL->scantime = sbuf.st_ctime;
  936.       mail_expunge (sysibx);    /* now expunge all those messages */
  937.     }
  938.     if (sysibx) mail_close (sysibx);
  939.     mm_nocritical (stream);    /* release critical */
  940.   }
  941.   mail_exists (stream,nmsgs);    /* notify upper level of mailbox size */
  942.   mail_recent (stream,recent);
  943.   return T;            /* return that we are alive */
  944. }
  945.  
  946. /* MH mail check mailbox
  947.  * Accepts: MAIL stream
  948.  */
  949.  
  950. void mh_check (stream)
  951.     MAILSTREAM *stream;
  952. {
  953.   /* Perhaps in the future this will preserve flags */
  954.   if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
  955. }
  956.  
  957.  
  958. /* MH mail expunge mailbox
  959.  * Accepts: MAIL stream
  960.  */
  961.  
  962. void mh_expunge (stream)
  963.     MAILSTREAM *stream;
  964. {
  965.   MESSAGECACHE *elt;
  966.   unsigned long j;
  967.   unsigned long i = 1;
  968.   unsigned long n = 0;
  969.   unsigned long recent = stream->recent;
  970.   mh_gc (stream,GC_TEXTS);    /* invalidate texts */
  971.   mm_critical (stream);        /* go critical */
  972.   while (i <= stream->nmsgs) {    /* for each message */
  973.                 /* if deleted, need to trash it */
  974.     if ((elt = mail_elt (stream,i))->deleted) {
  975.       sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  976.       if (unlink (LOCAL->buf)) {/* try to delete the message */
  977.     sprintf (LOCAL->buf,"Expunge of message %ld failed, aborted: %s",i,
  978.          strerror (errno));
  979.     mm_log (LOCAL->buf,(long) NIL);
  980.     break;
  981.       }
  982.       if (elt->recent) --recent;/* if recent, note one less recent message */
  983.       mail_expunged (stream,i);    /* notify upper levels */
  984.       n++;            /* count up one more expunged message */
  985.     }
  986.     else i++;            /* otherwise try next message */
  987.   }
  988.   if (n) {            /* output the news if any expunged */
  989.     sprintf (LOCAL->buf,"Expunged %ld messages",n);
  990.     mm_log (LOCAL->buf,(long) NIL);
  991.   }
  992.   else mm_log ("No messages deleted, so no update needed",(long) NIL);
  993.   mm_nocritical (stream);    /* release critical */
  994.                 /* notify upper level of new mailbox size */
  995.   mail_exists (stream,stream->nmsgs);
  996.   mail_recent (stream,recent);
  997. }
  998.  
  999. /* MH mail copy message(s)
  1000.     s;
  1001.  * Accepts: MAIL stream
  1002.  *        sequence
  1003.  *        destination mailbox
  1004.  * Returns: T if copy successful, else NIL
  1005.  */
  1006.  
  1007. long mh_copy (stream,sequence,mailbox)
  1008.     MAILSTREAM *stream;
  1009.     char *sequence;
  1010.     char *mailbox;
  1011. {
  1012.   STRING st;
  1013.   MESSAGECACHE *elt;
  1014.   struct stat sbuf;
  1015.   int fd;
  1016.   long i;
  1017.   char *s,tmp[MAILTMPLEN];
  1018.                 /* copy the messages */
  1019.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  1020.     if ((elt = mail_elt (stream,i))->sequence) {
  1021.       sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  1022.       if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
  1023.       fstat (fd,&sbuf);        /* get size of message */
  1024.                 /* slurp message */
  1025.       read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  1026.       s[sbuf.st_size] = '\0';    /* tie off file */
  1027.       close (fd);        /* flush message file */
  1028.       INIT (&st,mail_string,(void *) s,sbuf.st_size);
  1029.       sprintf (LOCAL->buf,"%s%s%s%s%s)",
  1030.            elt->seen ? " \\Seen" : "",
  1031.            elt->deleted ? " \\Deleted" : "",
  1032.            elt->flagged ? " \\Flagged" : "",
  1033.            elt->answered ? " \\Answered" : "",
  1034.            (elt->seen || elt->deleted || elt->flagged || elt->answered) ?
  1035.            "" : " ");
  1036.       LOCAL->buf[0] = '(';    /* open list */
  1037.       mail_date (tmp,elt);    /* generate internal date */
  1038.       if (!mh_append (stream,mailbox,LOCAL->buf,tmp,&st)) {
  1039.     fs_give ((void **) &s);    /* give back temporary space */
  1040.     return NIL;
  1041.       }
  1042.       fs_give ((void **) &s);    /* give back temporary space */
  1043.     }
  1044.   return T;            /* return success */
  1045. }
  1046.  
  1047. /* MH mail move message(s)
  1048.     s;
  1049.  * Accepts: MAIL stream
  1050.  *        sequence
  1051.  *        destination mailbox
  1052.  * Returns: T if move successful, else NIL
  1053.  */
  1054.  
  1055. long mh_move (stream,sequence,mailbox)
  1056.     MAILSTREAM *stream;
  1057.     char *sequence;
  1058.     char *mailbox;
  1059. {
  1060.   MESSAGECACHE *elt;
  1061.   if (mh_copy (stream,sequence,mailbox)) return NIL;
  1062.                 /* delete all requested messages */
  1063.   mh_setflag (stream,sequence,"\\Deleted");
  1064.   return T;
  1065. }
  1066.  
  1067. /* MH mail append message from stringstruct
  1068.  * Accepts: MAIL stream
  1069.  *        destination mailbox
  1070.  *        stringstruct of messages to append
  1071.  * Returns: T if append successful, else NIL
  1072.  */
  1073.  
  1074. long mh_append (stream,mailbox,flags,date,message)
  1075.     MAILSTREAM *stream;
  1076.     char *mailbox;
  1077.     char *flags;
  1078.     char *date;
  1079.              STRING *message;
  1080. {
  1081.   struct stat sbuf;
  1082.   struct direct **names;
  1083.   int fd;
  1084.   char c,*s,*t,tmp[MAILTMPLEN];
  1085.   MESSAGECACHE elt;
  1086.   long i,last,nfiles;
  1087.   long size = 0;
  1088.   long ret = LONGT;
  1089.   short f = 0;
  1090.   if (flags) {            /* get flags if given */
  1091.     MAILSTREAM *st = mail_open (NIL,mailbox,OP_SILENT);
  1092.     long ruf;
  1093.     f = mh_getflags (st,flags);
  1094.     mail_close (st);
  1095.   }
  1096.   if (date) {            /* want to preserve date? */
  1097.                 /* yes, parse date into an elt */
  1098.     if (!mail_parse_date (&elt,date)) {
  1099.       sprintf (tmp,"Bad date in append: %s",date);
  1100.       mm_log (tmp,ERROR);
  1101.       return NIL;
  1102.     }
  1103.   }
  1104.                 /* N.B.: can't use LOCAL->buf for tmp */
  1105.                 /* make sure valid mailbox */
  1106.   if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
  1107.   case ENOENT:            /* no such file? */
  1108.     mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  1109.     return NIL;
  1110.   case 0:            /* merely empty file? */
  1111.     break;
  1112.   case EINVAL:
  1113.     sprintf (tmp,"Invalid MH-format mailbox name: %s",mailbox);
  1114.     mm_log (tmp,ERROR);
  1115.     return NIL;
  1116.   default:
  1117.     sprintf (tmp,"Not a MH-format mailbox: %s",mailbox);
  1118.     mm_log (tmp,ERROR);
  1119.     return NIL;
  1120.   }
  1121.   mh_file (tmp,mailbox);    /* build file name we will use */
  1122.   if (nfiles = scandir (tmp,&names,mh_select,mh_numsort)) {
  1123.                 /* largest number */
  1124.     last = atoi (names[nfiles-1]->d_name);
  1125.     for (i = 0; i < nfiles; ++i) /* free directory */
  1126.       fs_give ((void **) &names[i]);
  1127.   }
  1128.   else last = 0;        /* no messages here yet */
  1129.   if (names) fs_give ((void **) &names);
  1130.  
  1131.   sprintf (tmp + strlen (tmp),"/%lu",++last);
  1132.   if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) {
  1133.     sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
  1134.     mm_log (tmp,ERROR);
  1135.     return NIL;
  1136.   }
  1137.   i = SIZE (message);        /* get size of message */
  1138.   s = (char *) fs_get (i + 1);    /* get space for the data */
  1139.                 /* copy the data w/o CR's */
  1140.   while (i--) if ((c = SNX (message)) != '\015') s[size++] = c;
  1141.   mm_critical (stream);        /* go critical */
  1142.                 /* write the data */
  1143.   if ((write (fd,s,size) < 0) || fsync (fd)) {
  1144.     unlink (tmp);        /* delete mailbox */
  1145.     sprintf (tmp,"Message append failed: %s",strerror (errno));
  1146.     mm_log (tmp,ERROR);
  1147.     ret = NIL;
  1148.   }
  1149.   close (fd);            /* close the file */
  1150.   mm_nocritical (stream);    /* release critical */
  1151.   fs_give ((void **) &s);    /* flush the buffer */
  1152.   return ret;
  1153. }
  1154.  
  1155. /* MH garbage collect stream
  1156.  * Accepts: Mail stream
  1157.  *        garbage collection flags
  1158.  */
  1159.  
  1160. void mh_gc (stream,gcflags)
  1161.     MAILSTREAM *stream;
  1162.     long gcflags;
  1163. {
  1164.   unsigned long i;
  1165.   if (gcflags & GC_TEXTS) {    /* garbage collect texts? */
  1166.                 /* flush texts from cache */
  1167.     if (LOCAL->hdr) fs_give ((void **) &LOCAL->hdr);
  1168.     if (stream->text) fs_give ((void **) &stream->text);
  1169.     stream->msgno = 0;        /* invalidate stream text */
  1170.   }
  1171. }
  1172.  
  1173. /* Internal routines */
  1174.  
  1175.  
  1176. /* MH file name selection test
  1177.  * Accepts: candidate directory entry
  1178.  * Returns: T to use file name, NIL to skip it
  1179.  */
  1180.  
  1181. int mh_select (name)
  1182.     struct direct *name;
  1183. {
  1184.   char c;
  1185.   char *s = name->d_name;
  1186.   while (c = *s++) if (!isdigit (c)) return NIL;
  1187.   return T;
  1188. }
  1189.  
  1190.  
  1191. /* MH file name comparision
  1192.  * Accepts: first candidate directory entry
  1193.  *        second candidate directory entry
  1194.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  1195.  */
  1196.  
  1197. int mh_numsort (d1,d2)
  1198.     struct direct **d1;
  1199.     struct direct **d2;
  1200. {
  1201.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  1202. }
  1203.  
  1204.  
  1205. /* MH mail build file name
  1206.  * Accepts: destination string
  1207.  *          source
  1208.  * Returns: destination
  1209.  */
  1210.  
  1211. char *mh_file (dst,name)
  1212.     char *dst;
  1213.     char *name;
  1214. {
  1215.   char tmp[MAILTMPLEN];
  1216.                 /* build composite name */
  1217.   sprintf (dst,"%s/%s",mh_path,strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") ?
  1218.        name + 4 : "inbox");
  1219.   return dst;
  1220. }
  1221.  
  1222. /* Parse flag list
  1223.  * Accepts: MAIL stream
  1224.  *        flag list as a character string
  1225.  * Returns: flag command list
  1226.  */
  1227.  
  1228. short mh_getflags (stream,flag)
  1229.     MAILSTREAM *stream;
  1230.     char *flag;
  1231. {
  1232.   char *t,tmp[MAILTMPLEN],err[MAILTMPLEN];
  1233.   short f = 0;
  1234.   short i,j;
  1235.   if (flag && *flag) {        /* no-op if no flag string */
  1236.                 /* check if a list and make sure valid */
  1237.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1238.       mm_log ("Bad flag list",ERROR);
  1239.       return NIL;
  1240.     }
  1241.                 /* copy the flag string w/o list construct */
  1242.     strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
  1243.     tmp[j] = '\0';
  1244.     t = ucase (tmp);        /* uppercase only from now on */
  1245.  
  1246.     while (t && *t) {        /* parse the flags */
  1247.       if (*t == '\\') {        /* system flag? */
  1248.     switch (*++t) {        /* dispatch based on first character */
  1249.     case 'S':        /* possible \Seen flag */
  1250.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1251.       t += 4;        /* skip past flag name */
  1252.       break;
  1253.     case 'D':        /* possible \Deleted flag */
  1254.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1255.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1256.       t += 7;        /* skip past flag name */
  1257.       break;
  1258.     case 'F':        /* possible \Flagged flag */
  1259.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1260.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1261.       t += 7;        /* skip past flag name */
  1262.       break;
  1263.     case 'A':        /* possible \Answered flag */
  1264.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1265.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1266.       t += 8;        /* skip past flag name */
  1267.       break;
  1268.     default:        /* unknown */
  1269.       i = 0;
  1270.       break;
  1271.     }
  1272.                 /* add flag to flags list */
  1273.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1274.       }
  1275.       else {            /* no user flags yet */
  1276.     t = strtok (t," ");    /* isolate flag name */
  1277.     sprintf (err,"Unknown flag: %.80s",t);
  1278.     t = strtok (NIL," ");    /* get next flag */
  1279.     mm_log (err,ERROR);
  1280.       }
  1281.     }
  1282.   }
  1283.   return f;
  1284. }
  1285.  
  1286. /* Search support routines
  1287.  * Accepts: MAIL stream
  1288.  *        message number
  1289.  *        pointer to additional data
  1290.  *        pointer to temporary buffer
  1291.  * Returns: T if search matches, else NIL
  1292.  */
  1293.  
  1294. char mh_search_all (stream,msgno,d,n)
  1295.     MAILSTREAM *stream;
  1296.     long msgno;
  1297.     char *d;
  1298.     long n;
  1299. {
  1300.   return T;            /* ALL always succeeds */
  1301. }
  1302.  
  1303.  
  1304. char mh_search_answered (stream,msgno,d,n)
  1305.     MAILSTREAM *stream;
  1306.     long msgno;
  1307.     char *d;
  1308.     long n;
  1309. {
  1310.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1311. }
  1312.  
  1313.  
  1314. char mh_search_deleted (stream,msgno,d,n)
  1315.     MAILSTREAM *stream;
  1316.     long msgno;
  1317.     char *d;
  1318.     long n;
  1319. {
  1320.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1321. }
  1322.  
  1323.  
  1324. char mh_search_flagged (stream,msgno,d,n)
  1325.     MAILSTREAM *stream;
  1326.     long msgno;
  1327.     char *d;
  1328.     long n;
  1329. {
  1330.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1331. }
  1332.  
  1333.  
  1334. char mh_search_keyword (stream,msgno,d,n)
  1335.     MAILSTREAM *stream;
  1336.     long msgno;
  1337.     char *d;
  1338.     long n;
  1339. {
  1340.   return NIL;            /* keywords not supported yet */
  1341. }
  1342.  
  1343.  
  1344. char mh_search_new (stream,msgno,d,n)
  1345.     MAILSTREAM *stream;
  1346.     long msgno;
  1347.     char *d;
  1348.     long n;
  1349. {
  1350.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1351.   return (elt->recent && !elt->seen) ? T : NIL;
  1352. }
  1353.  
  1354. char mh_search_old (stream,msgno,d,n)
  1355.     MAILSTREAM *stream;
  1356.     long msgno;
  1357.     char *d;
  1358.     long n;
  1359. {
  1360.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1361. }
  1362.  
  1363.  
  1364. char mh_search_recent (stream,msgno,d,n)
  1365.     MAILSTREAM *stream;
  1366.     long msgno;
  1367.     char *d;
  1368.     long n;
  1369. {
  1370.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1371. }
  1372.  
  1373.  
  1374. char mh_search_seen (stream,msgno,d,n)
  1375.     MAILSTREAM *stream;
  1376.     long msgno;
  1377.     char *d;
  1378.     long n;
  1379. {
  1380.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1381. }
  1382.  
  1383.  
  1384. char mh_search_unanswered (stream,msgno,d,n)
  1385.     MAILSTREAM *stream;
  1386.     long msgno;
  1387.     char *d;
  1388.     long n;
  1389. {
  1390.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1391. }
  1392.  
  1393.  
  1394. char mh_search_undeleted (stream,msgno,d,n)
  1395.     MAILSTREAM *stream;
  1396.     long msgno;
  1397.     char *d;
  1398.     long n;
  1399. {
  1400.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1401. }
  1402.  
  1403.  
  1404. char mh_search_unflagged (stream,msgno,d,n)
  1405.     MAILSTREAM *stream;
  1406.     long msgno;
  1407.     char *d;
  1408.     long n;
  1409. {
  1410.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1411. }
  1412.  
  1413.  
  1414. char mh_search_unkeyword (stream,msgno,d,n)
  1415.     MAILSTREAM *stream;
  1416.     long msgno;
  1417.     char *d;
  1418.     long n;
  1419. {
  1420.   return T;            /* keywords not supported yet */
  1421. }
  1422.  
  1423.  
  1424. char mh_search_unseen (stream,msgno,d,n)
  1425.     MAILSTREAM *stream;
  1426.     long msgno;
  1427.     char *d;
  1428.     long n;
  1429. {
  1430.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1431. }
  1432.  
  1433. char mh_search_before (stream,msgno,d,n)
  1434.     MAILSTREAM *stream;
  1435.     long msgno;
  1436.     char *d;
  1437.     long n;
  1438. {
  1439.   return (char) (mh_msgdate (stream,msgno) < n);
  1440. }
  1441.  
  1442.  
  1443. char mh_search_on (stream,msgno,d,n)
  1444.     MAILSTREAM *stream;
  1445.     long msgno;
  1446.     char *d;
  1447.     long n;
  1448. {
  1449.   return (char) (mh_msgdate (stream,msgno) == n);
  1450. }
  1451.  
  1452.  
  1453. char mh_search_since (stream,msgno,d,n)
  1454.     MAILSTREAM *stream;
  1455.     long msgno;
  1456.     char *d;
  1457.     long n;
  1458. {
  1459.                 /* everybody interprets "since" as .GE. */
  1460.   return (char) (mh_msgdate (stream,msgno) >= n);
  1461. }
  1462.  
  1463.  
  1464. unsigned long mh_msgdate (stream,msgno)
  1465.     MAILSTREAM *stream;
  1466.     long msgno;
  1467. {
  1468.   struct stat sbuf;
  1469.   struct tm *tm;
  1470.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1471.   if (!elt->day) {        /* get date if don't have it yet */
  1472.                 /* build message file name */
  1473.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  1474.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1475.     tm = gmtime (&sbuf.st_mtime);
  1476.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1477.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1478.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1479.     elt->seconds = tm->tm_sec;
  1480.     elt->zhours = 0; elt->zminutes = 0;
  1481.   }
  1482.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1483. }
  1484.  
  1485. char mh_search_body (stream,msgno,d,n)
  1486.     MAILSTREAM *stream;
  1487.     long msgno;
  1488.     char *d;
  1489.     long n;
  1490. {
  1491.   mh_fetchheader (stream,msgno);
  1492.   return stream->text ? search (stream->text,strlen (stream->text),d,n) : NIL;
  1493. }
  1494.  
  1495.  
  1496. char mh_search_subject (stream,msgno,d,n)
  1497.     MAILSTREAM *stream;
  1498.     long msgno;
  1499.     char *d;
  1500.     long n;
  1501. {
  1502.   char *t = mh_fetchstructure (stream,msgno,NIL)->subject;
  1503.   return t ? search (t,strlen (t),d,n) : NIL;
  1504. }
  1505.  
  1506.  
  1507. char mh_search_text (stream,msgno,d,n)
  1508.     MAILSTREAM *stream;
  1509.     long msgno;
  1510.     char *d;
  1511.     long n;
  1512. {
  1513.   char *t = mh_fetchheader (stream,msgno);
  1514.   return (t && search (t,strlen (t),d,n)) ||
  1515.     mh_search_body (stream,msgno,d,n);
  1516. }
  1517.  
  1518. char mh_search_bcc (stream,msgno,d,n)
  1519.     MAILSTREAM *stream;
  1520.     long msgno;
  1521.     char *d;
  1522.     long n;
  1523. {
  1524.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->bcc;
  1525.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1526.                 /* get text for address */
  1527.   rfc822_write_address (LOCAL->buf,a);
  1528.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1529. }
  1530.  
  1531.  
  1532. char mh_search_cc (stream,msgno,d,n)
  1533.     MAILSTREAM *stream;
  1534.     long msgno;
  1535.     char *d;
  1536.     long n;
  1537. {
  1538.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->cc;
  1539.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1540.                 /* get text for address */
  1541.   rfc822_write_address (LOCAL->buf,a);
  1542.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1543. }
  1544.  
  1545.  
  1546. char mh_search_from (stream,msgno,d,n)
  1547.     MAILSTREAM *stream;
  1548.     long msgno;
  1549.     char *d;
  1550.     long n;
  1551. {
  1552.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->from;
  1553.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1554.                 /* get text for address */
  1555.   rfc822_write_address (LOCAL->buf,a);
  1556.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1557. }
  1558.  
  1559.  
  1560. char mh_search_to (stream,msgno,d,n)
  1561.     MAILSTREAM *stream;
  1562.     long msgno;
  1563.     char *d;
  1564.     long n;
  1565. {
  1566.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->to;
  1567.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1568.                 /* get text for address */
  1569.   rfc822_write_address (LOCAL->buf,a);
  1570.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1571. }
  1572.  
  1573. /* Search parsers */
  1574.  
  1575.  
  1576. /* Parse a date
  1577.  * Accepts: function to return
  1578.  *        pointer to date integer to return
  1579.  * Returns: function to return
  1580.  */
  1581.  
  1582. search_t mh_search_date (f,n)
  1583.     search_t f;
  1584.     long *n;
  1585. {
  1586.   long i;
  1587.   char *s;
  1588.   MESSAGECACHE elt;
  1589.                 /* parse the date and return fn if OK */
  1590.   return (mh_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1591.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1592. }
  1593.  
  1594. /* Parse a flag
  1595.  * Accepts: function to return
  1596.  *        pointer to string to return
  1597.  * Returns: function to return
  1598.  */
  1599.  
  1600. search_t mh_search_flag (f,d)
  1601.     search_t f;
  1602.     char **d;
  1603. {
  1604.                 /* get a keyword, return if OK */
  1605.   return (*d = strtok (NIL," ")) ? f : NIL;
  1606. }
  1607.  
  1608.  
  1609. /* Parse a string
  1610.  * Accepts: function to return
  1611.  *        pointer to string to return
  1612.  *        pointer to string length to return
  1613.  * Returns: function to return
  1614.  */
  1615.  
  1616. search_t mh_search_string (f,d,n)
  1617.     search_t f;
  1618.     char **d;
  1619.     long *n;
  1620. {
  1621.   char *end = " ";
  1622.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1623.   if (!c) return NIL;        /* missing argument */
  1624.   switch (*c) {            /* see what the argument is */
  1625.   case '{':            /* literal string */
  1626.     *n = strtol (c+1,d,10);    /* get its length */
  1627.     if ((*(*d)++ == '}') && (*(*d)++ == '\015') && (*(*d)++ == '\012') &&
  1628.     (!(*(c = *d + *n)) || (*c == ' '))) {
  1629.       char e = *--c;
  1630.       *c = DELIM;        /* make sure not a space */
  1631.       strtok (c," ");        /* reset the strtok mechanism */
  1632.       *c = e;            /* put character back */
  1633.       break;
  1634.     }
  1635.   case '\0':            /* catch bogons */
  1636.   case ' ':
  1637.     return NIL;
  1638.   case '"':            /* quoted string */
  1639.     if (strchr (c+1,'"')) end = "\"";
  1640.     else return NIL;
  1641.   default:            /* atomic string */
  1642.     if (*d = strtok (c,end)) *n = strlen (*d);
  1643.     else return NIL;
  1644.     break;
  1645.   }
  1646.   return f;
  1647. }
  1648.